Skip to content

Preserve HTTPS angle-bracket autolinks in safe-output sanitization#48169

Merged
pelikhan merged 10 commits into
mainfrom
copilot/fix-slack-mrkdwn-links
Jul 27, 2026
Merged

Preserve HTTPS angle-bracket autolinks in safe-output sanitization#48169
pelikhan merged 10 commits into
mainfrom
copilot/fix-slack-mrkdwn-links

Conversation

Copilot AI commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

Custom safe-output job string inputs were sanitizing Slack/CommonMark autolinks like <https://...|label> as if they were unknown HTML tags, rewriting them to parentheses before they reached downstream sinks. This preserves those HTTPS angle-bracket links while keeping existing URL allowlist filtering in place.

  • Sanitizer behavior

    • Teach convertXmlTags to recognize HTTPS angle-bracket autolinks and leave them intact instead of treating them as XML/HTML.
    • Cover both plain autolinks and Slack mrkdwn labeled links:
      • <https://example.com>
      • <https://example.com|label>
  • URL policy preservation

    • Extend domain sanitization to process angle-bracket HTTPS autolinks as a single unit.
    • Allowed domains continue to pass through unchanged.
    • Disallowed domains still redact, including inside Slack-style links.
  • Regression coverage

    • Add sanitizer tests for preserved HTTPS autolinks and Slack mrkdwn links.
    • Add a custom safe-job ingestion regression to verify safe-output string inputs retain Slack mrkdwn after NDJSON collection.
    • Update the existing markdown-link expectation for angle-bracket HTTPS URLs to reflect the new preserved behavior.
Before: Tracking issue: (https://github.com/octo-org/octo-repo/issues/123|Build failure)
After:  Tracking issue: <https://github.com/octo-org/octo-repo/issues/123|Build failure>

Generated by 👨‍🍳 PR Sous Chef · gpt54 · 22.4 AIC · ⌖ 9.38 AIC · ⊞ 7.1K ·
Comment /souschef to run again


Generated by 👨‍🍳 PR Sous Chef · gpt54 · 23.1 AIC · ⌖ 6.65 AIC · ⊞ 7.1K ·
Comment /souschef to run again


Generated by 👨‍🍳 PR Sous Chef · gpt54 · 17.5 AIC · ⌖ 7.28 AIC · ⊞ 7.1K ·
Comment /souschef to run again


Generated by 👨‍🍳 PR Sous Chef · gpt54 · 28.5 AIC · ⌖ 13 AIC · ⊞ 7.1K ·
Comment /souschef to run again


run: https://github.com/github/gh-aw/actions/runs/30207935610

Generated by 👨‍🍳 PR Sous Chef · gpt54 · 23.3 AIC · ⌖ 7.77 AIC · ⊞ 7.1K ·
Comment /souschef to run again


Generated by 👨‍🍳 PR Sous Chef · gpt54 · 17.1 AIC · ⌖ 7.08 AIC · ⊞ 7.1K ·
Comment /souschef to run again


Generated by 👨‍🍳 PR Sous Chef · gpt54 · 17.1 AIC · ⌖ 7.08 AIC · ⊞ 7.1K ·
Comment /souschef to run again


Generated by 👨‍🍳 PR Sous Chef · gpt54 · 14.5 AIC · ⌖ 7.42 AIC · ⊞ 7.1K ·
Comment /souschef to run again


Run: https://github.com/github/gh-aw/actions/runs/30218624846

Generated by 👨‍🍳 PR Sous Chef · gpt54 · 19 AIC · ⌖ 8.59 AIC · ⊞ 7.1K ·
Comment /souschef to run again


Generated by 👨‍🍳 PR Sous Chef · gpt54 · 15.3 AIC · ⌖ 8.16 AIC · ⊞ 7.1K ·
Comment /souschef to run again

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix safe-outputs sanitizer handling of Slack mrkdwn links Preserve HTTPS angle-bracket autolinks in safe-output sanitization Jul 26, 2026
Copilot AI requested a review from pelikhan July 26, 2026 08:52
@pelikhan
pelikhan marked this pull request as ready for review July 26, 2026 09:24
Copilot AI review requested due to automatic review settings July 26, 2026 09:24

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Preserves HTTPS/CommonMark and Slack mrkdwn autolinks during safe-output sanitization while retaining domain filtering.

Changes:

  • Preserves angle-bracket HTTPS links.
  • Adds domain handling for labeled autolinks.
  • Adds sanitizer and ingestion regressions.
Show a summary per file
File Description
actions/setup/js/sanitize_content_core.cjs Preserves and filters HTTPS autolinks.
actions/setup/js/sanitize_content.test.cjs Tests sanitizer behavior.
actions/setup/js/collect_ndjson_output.test.cjs Tests custom safe-job ingestion.

Review details

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

  • Files reviewed: 3/3 changed files
  • Comments generated: 1
  • Review effort level: Medium

* @returns {boolean}
*/
function isHttpsAngleBracketAutolink(tagContent) {
return /^https:\/\/[^\s<>|]+(?:\|[^<>]*)?$/i.test(tagContent);
@github-actions

github-actions Bot commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

🧠 Matt Pocock Skills Reviewer has completed the skills-based review. ✅

@github-actions

github-actions Bot commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

Test Quality Sentinel completed test quality analysis.

@github-actions

github-actions Bot commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

Design Decision Gate 🏗️ completed the design decision gate check.

No ADR enforcement needed: PR does not have the 'implementation' label and has ≤100 new lines of code in business logic directories (0 additions detected).

@github-actions

github-actions Bot commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

PR Code Quality Reviewer completed the code quality review.

@github-actions

Copy link
Copy Markdown
Contributor

Thanks for the fix! 🎉 This PR looks great — it clearly solves issue #47643 by teaching the sanitizer to recognize and preserve HTTPS angle-bracket autolinks (both plain and Slack mrkdwn forms) while maintaining URL domain filtering.

What's working well:

  • ✅ Focused change: teaches convertXmlTags to recognize autolinks and skip them
  • ✅ URL policy preserved: domain sanitization still applies to filtered autolinks
  • ✅ Comprehensive test coverage: regression tests for plain HTTPS autolinks, Slack mrkdwn labeled links, and integration test verifying mrkdwn survives NDJSON collection
  • ✅ Clear before/after example in the PR description
  • ✅ No new dependencies

This is ready for review and merge. Great work!

Generated by ✅ Contribution Check · sonnet46 · 61.7 AIC · ⌖ 10.4 AIC · ⊞ 6.2K ·

@github-actions

Copy link
Copy Markdown
Contributor

🧪 Test Quality Sentinel Report

Test Quality Score: 100/100 — Excellent

Analyzed 4 test(s): 4 design, 0 implementation, 0 violation(s).

📊 Metrics (4 tests)
Metric Value
Analyzed 4 (Go: 0, JS: 4)
✅ Design 4 (100%)
⚠️ Implementation 0 (0%)
Edge/error coverage 4 (100%)
Duplicate clusters 0
Inflation ✅ No
🚨 Violations 0
Test File Classification
Modified: "inline link with angle-bracket URL" sanitize_content.test.cjs design_test
New: "preserve HTTPS angle-bracket autolinks" sanitize_content.test.cjs design_test
New: "preserve Slack mrkdwn links on allowed HTTPS domains" sanitize_content.test.cjs design_test
New: "preserve Slack mrkdwn links in custom safe-job string inputs" collect_ndjson_output.test.cjs design_test

Quality Highlights

Excellent test design:

  • All 4 tests verify specific behavioral contracts (preservation of HTTPS autolinks and Slack mrkdwn format)
  • 1 modified test corrects expected behavior to match new design intent
  • 2 unit tests in sanitize_content.test.cjs validate core sanitization logic
  • 1 end-to-end integration test validates full pipeline through NDJSON output collection

Verdict

Passed. 0% implementation tests (threshold: 30%). All tests verify design-level contracts with no violations.

🧪 Test quality analysis by Test Quality Sentinel · haiku45 · 14 AIC · ⌖ 7.54 AIC · ⊞ 8.1K ·
Comment /review to run again

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: Preserve HTTPS angle-bracket autolinks in safe-output sanitization

The fix is correct and well-structured — isHttpsAngleBracketAutolink guards convertXmlTags before tag rewriting, and the first-pass replacement in sanitizeUrlDomains handles Slack mrkdwn links as a unit. Two non-blocking issues found:

  1. Path regex excludes | too eagerly (line 269): [^\s<>|]* stops at |, so URLs with | in query strings are mis-parsed. Use [^\s<>]* and split the Slack label at the last |.
  2. Missing test for disallowed Slack-style links: The label text is silently dropped when the domain is blocked. A test should pin this behavior.

No security or blocking correctness issues found beyond the existing IPv6 comment.

🧵 Reviewed using Impeccable skills by Impeccable Skills Reviewer · sonnet46 · 30.1 AIC · ⌖ 5.73 AIC · ⊞ 5K

* @returns {string} The string with unknown domains redacted
*/
function sanitizeUrlDomains(s, allowed) {
const angleBracketHttpsAutolinkRegex = /<https:\/\/([\w.-]+(?::\d+)?)(\/[^\s<>|]*)?(?:\|([^<>]*))?>/gi;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The path capture group [^\s<>|]* stops at |, so a URL with a literal | in its query string (e.g. (example.com/redacted)) is parsed incorrectly — the path is truncated and the remainder treated as a Slack label. While bare | in URLs is uncommon, it is valid per RFC 3986. Consider capturing the path as [^\s<>]* and splitting off the Slack label only at the last |, or document this known limitation.

@copilot please address this.


it("should handle CDATA sections", () => {
const result = sanitizeContent("<![CDATA[<script>alert('xss')</script>]]>");
expect(result).toBe("(![CDATA[(script)alert('xss')(/script)]])");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no test covering a Slack mrkdwn link whose domain is not in the allowlist (e.g. (evil.com/redacted)). The current redaction path in sanitizeUrlDomains silently drops the label text when the domain is blocked — the output is (evil.com/redacted) rather than something like (evil.com/redacted|click here). Please add a test to pin the expected output for disallowed Slack-style links so regressions are caught.

@copilot please address this.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Test Quality Sentinel: 100/100. 0% implementation tests (threshold: 30%).

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Skills-Based Review 🧠

Applied /diagnosing-bugs and /tdd — requesting changes on one correctness defect and its missing regression test.

📋 Key Themes & Highlights

Key Issues

  • Label-loss bug (sanitize_content_core.cjs line 328): when a Slack mrkdwn link contains a disallowed domain, applyDomainFilter returns (hostname/redacted) and the callback returns that bare string, silently discarding the angle brackets and the |label text entirely.
  • Missing test: the new test suite covers the happy path (allowed domain) but has no case for a disallowed domain in a Slack link, so the bug above is undetected.

Positive Highlights

  • ✅ Root-cause fix is well-targeted: isHttpsAngleBracketAutolink correctly gates only HTTPS forms.
  • ✅ End-to-end integration test in collect_ndjson_output.test.cjs is a good addition.
  • ✅ The two-pass strategy (handle angle-bracket links before generic URL matching) is the right approach to avoid greedy regex overlap.

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer · sonnet46 · 35.4 AIC · ⌖ 4.8 AIC · ⊞ 6.7K
Comment /matt to run again

s = s.replace(angleBracketHttpsAutolinkRegex, (match, hostnameWithPort, path = "") => {
const url = `https://${hostnameWithPort}${path || ""}`;
const filtered = applyDomainFilter(url, hostnameWithPort);
return filtered === url ? match : filtered;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[/diagnosing-bugs] Slack label is silently dropped when the domain is disallowed. When applyDomainFilter redacts a disallowed domain, filtered becomes "(hostname/redacted)" — the outer angle brackets and |label text are lost entirely.

💡 Fix: reconstruct the full Slack link on redaction

The callback receives the full match (e.g. (evil.com/redacted)) and the label via capture group 3. When the domain is filtered you should reconstruct an appropriate replacement that preserves the label:

s = s.replace(angleBracketHttpsAutolinkRegex, (match, hostnameWithPort, path = "", label) => {
  const url = `https://${hostnameWithPort}${path || ""}`;
  const filtered = applyDomainFilter(url, hostnameWithPort);
  if (filtered === url) return match; // allowed — preserve original angle-bracket form
  // Disallowed — redact URL but keep label if present
  return label ? `${filtered}|${label}` : filtered;
});

Without this, a message like (evil.com/redacted) silently becomes (evil.com/redacted), discarding the visible label text the author intended.

@copilot please address this.

it("should preserve Slack mrkdwn links on allowed HTTPS domains", () => {
const input = "Tracking issue: <https://github.com/octo-org/octo-repo/issues/123|Build failure — Build github/gh-aw#456>";
expect(sanitizeContent(input)).toBe(input);
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[/tdd] No test covers the disallowed-domain path for Slack mrkdwn links — the label-loss defect (line 328 in sanitize_content_core.cjs) is invisible without it.

💡 Add a companion test for the disallowed-domain case
it("should redact disallowed domain in Slack mrkdwn link while preserving label", () => {
  const input = "See: (evil.example.com/redacted)";
  const result = sanitizeContent(input);
  // URL should be redacted but label text retained
  expect(result).not.toContain("evil.example.com");
  expect(result).toContain("Build failure");
});

Without this test the current code silently discards Build failure, and a future refactor has no safety net.

@copilot please address this.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non-blocking observations — the fix is correct and the two-pass approach (preserve in convertXmlTags, then domain-filter in sanitizeUrlDomains) is sound.

### Review findings

Medium — regex allocation per call

angleBracketHttpsAutolinkRegex is defined inside sanitizeUrlDomains, so a new RegExp object is allocated on every invocation. Hoist it to module-level (same pattern as httpsUrlRegex). See inline comment.

Medium — no redaction test for blocked-domain Slack links

The new tests cover the happy path (github.com allowed) but nothing asserts that (evil.com/redacted) gets redacted and its label is dropped. The silent label-drop behavior is also undocumented. See inline comment.

Low — duplicate "First pass" comment labels

Both passes in sanitizeUrlDomains are labeled "First pass", making execution order unclear. See inline comment.

🔎 Code quality review by PR Code Quality Reviewer · sonnet46 · 70.7 AIC · ⌖ 6.44 AIC · ⊞ 5.7K
Comment /review to run again

* @returns {string} The string with unknown domains redacted
*/
function sanitizeUrlDomains(s, allowed) {
const angleBracketHttpsAutolinkRegex = /<https:\/\/([\w.-]+(?::\d+)?)(\/[^\s<>|]*)?(?:\|([^<>]*))?>/gi;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Regex recompiled on every sanitizeUrlDomains call: angleBracketHttpsAutolinkRegex is defined inside the function, creating a new RegExp object on every invocation.

💡 Suggested fix

Hoist it to module-level alongside the other URL regexes (httpsUrlRegex, etc.) already defined at file scope:

// module-level, near the other URL-related regexes
const angleBracketHttpsAutolinkRegex = /<https:\/\/(\w[\w.-]*(?::\d+)?)(\/[^\s<>|]*)?(?:\|([^<>]*))?>/gi;

String.prototype.replace resets lastIndex automatically so hoisting with the g flag is safe here.

sanitizeUrlDomains is called per non-code region on every agent output string; allocating a new regex object each time is unnecessary overhead.


// First pass: handle angle-bracket autolinks and Slack mrkdwn links as a unit so
// later generic URL matching does not consume the trailing ">" or "|label".
s = s.replace(angleBracketHttpsAutolinkRegex, (match, hostnameWithPort, path = "") => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Disallowed-domain Slack mrkdwn links have no test coverage for redaction: The new angle-bracket first pass handles blocked domains, but there is no test asserting that (evil.com/redacted) is redacted and its label is dropped.

💡 Suggested test addition

Add a test in sanitize_content.test.cjs that verifies a disallowed-domain Slack-style link is redacted:

it("should redact Slack mrkdwn links with disallowed domains", () => {
  const input = "See (evil.com/redacted)";
  const result = sanitizeContent(input);
  expect(result).not.toContain("evil.com");
  expect(result).not.toContain("Click here"); // label is lost on redaction — document this
});

Without this, a regression that accidentally passes the label through for blocked domains would be undetected. The current behavior (silently dropping the label when the domain is blocked) is also worth a comment since it's a non-obvious behavioral contract.

return filtered === url ? match : filtered;
});

// First pass: handle explicit https:// URLs

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplicate "First pass" comment label: Both the angle-bracket pass (line 323) and the generic HTTPS pass (line 331) are labeled "First pass", making the ordering confusing to readers.

💡 Suggested fix

Rename the comments to reflect their actual execution order:

// Pass 1: handle angle-bracket autolinks and Slack mrkdwn links as a unit ...
s = s.replace(angleBracketHttpsAutolinkRegex, ...);

// Pass 2: handle remaining explicit https:// URLs
s = s.replace(httpsUrlRegex, ...);

@gh-aw-bot

Copy link
Copy Markdown
Collaborator

@copilot please run the pr-finisher skill, address the unresolved review threads about blocked-domain handling and URL parsing edge cases, and summarize any remaining blocker for maintainers.

Unresolved review feedback:

Please cover both code and test follow-up in one pass.

Generated by 👨🍳 PR Sous Chef
Comment /souschef to run again

Generated by 👨‍🍳 PR Sous Chef · gpt54 · 22.4 AIC · ⌖ 9.38 AIC · ⊞ 7.1K ·
Comment /souschef to run again

@pelikhan

Copy link
Copy Markdown
Collaborator

@copilot add tests in fuzz tests for parser

Copilot AI commented Jul 26, 2026

Copy link
Copy Markdown
Contributor Author

@copilot please run the pr-finisher skill, review the current branch state, address any still-open reviewer feedback and failed checks below, and reply with whether this PR is ready for maintainer r...

Current state verified:

Reviews — The 4 referenced review signals (kwDOPc1QR88...) aren't yet visible in the API; they are likely from the check run that started at 18:19 and may still be processing. All currently-visible reviews are based on the original commit b965f41. Every issue those reviews identified has been fixed in the current HEAD (f71cd08):

  • CHANGES_REQUESTED (Matt Pocock Skills Reviewer): both issues it flagged (label-loss bug + missing test) are fixed
  • All COMMENTED reviews: all inline suggestions implemented (regex hoisted, Pass 1/2 labels, |-limitation documented, IPv6/userinfo rejected, disallowed-domain tests added)
  • APPROVED (Test Quality Sentinel): still valid

Checks — All 6 completed CI runs show success. One run is in-progress (started 18:19); no failures to address.

Mergeableblocked only due to the stale CHANGES_REQUESTED review, not due to any code issue.

No further code changes are needed. Ready for maintainer action:

  1. Dismiss the stale CHANGES_REQUESTED review (Matt Pocock Skills Reviewer) — all its requested changes are in HEAD
  2. Re-trigger CI after dismissal for a green signal on current HEAD

@github-actions

Copy link
Copy Markdown
Contributor

🤖 PR Triage — §30214847023

Field Value
Category bug
Risk 🟢 Low
Priority 68/100
Breakdown Impact 28 · Urgency 22 · Quality 18
Action fast_track

Rationale: HTTPS angle-bracket autolink preservation in safe-output sanitization. Low-risk: 139 additions focused on test files + sanitize_content_core.cjs. CI passing (copilot: success). Multiple AI reviewers approved. Well-tested with fuzz test added. Ready for expedited human review.

Generated by 🔧 PR Triage Agent · sonnet46 · 34.1 AIC · ⌖ 7.94 AIC · ⊞ 5.7K ·

@gh-aw-bot

Copy link
Copy Markdown
Collaborator

@copilot please run the skill, review the current branch state, address any still-open reviewer feedback and failed checks, and reply with whether this PR is ready for maintainer review.

Run: https://github.com/github/gh-aw/actions/runs/30215321924

Generated by 👨‍🍳 PR Sous Chef · gpt54 · 16.9 AIC · ⌖ 7.13 AIC · ⊞ 5.6K ·
Comment /souschef to run again

Copilot AI commented Jul 26, 2026

Copy link
Copy Markdown
Contributor Author

@copilot please run the skill, review the current branch state, address any still-open reviewer feedback and failed checks, and reply with whether this PR is ready for maintainer review.

Run: https:...

Current branch state verified — no further code changes are required.

Local validation:

  • make agent-report-progress-no-test — all 262 workflows compile, lock files in sync, lint clean
  • ✅ 516/516 JS tests pass (sanitize_content.test.cjs 453 + sanitize_content_core_parser.test.cjs 63)

Review threads: All 8 threads are addressed in commits 5144310 and f71cd08:

Thread Status in HEAD
r3652183109 IPv6/userinfo bypass [\w.-]+ hostname pattern rejects [ and @; rejection tests added
r3652200991 | in URL path limitation ✅ Documented in module-level comment block
r3652200993 Missing test for disallowed Slack mrkdwn should redact Slack mrkdwn links with disallowed domains test at line 606
r3652201221 Label dropped on disallowed domain ✅ Callback returns `` `${filtered}
r3652201222 Missing label-preservation test ✅ Same test asserts expect(result).toContain("Build failure")
r3652207470 Regex allocated per call angleBracketHttpsAutolinkRegex hoisted to module scope at line 40
r3652207471 No redaction test for blocked Slack link ✅ Same test as r3652200993
r3652207473 Duplicate "First pass" comments ✅ Renamed to "Pass 1" / "Pass 2" at lines 347/366

PR status:

  • ✅ Reviews (code) — all requested changes implemented
  • ✅ Checks (local) — all tests pass
  • ❓ Checks (CI) — stale after agent push; needs human re-trigger
  • ❌ Mergeable — blocked by stale CHANGES_REQUESTED review from Matt Pocock Skills Reviewer bot (all its requests are now implemented)

Hand-off for maintainers:

  1. Dismiss the stale CHANGES_REQUESTED review — all its requested changes are in HEAD f71cd08
  2. Resolve the 2 non-outdated threads (r3652200993, r3652201222) — satisfied by existing tests
  3. Re-trigger CI (close/reopen or workflow_dispatch) for a green signal on current HEAD

@gh-aw-bot

Copy link
Copy Markdown
Collaborator

@copilot please run the pr-finisher skill, review the current branch state, address the unresolved feedback below, and reply with whether this PR is ready for maintainer review.

Unresolved review threads (newest first):

Generated by 👨‍🍳 PR Sous Chef · gpt54 · 19 AIC · ⌖ 8.59 AIC · ⊞ 7.1K ·
Comment /souschef to run again

Copilot AI commented Jul 26, 2026

Copy link
Copy Markdown
Contributor Author

@copilot please run the pr-finisher skill, review the current branch state, address the unresolved feedback below, and reply with whether this PR is ready for maintainer review.

Unresolved review t...

No further code changes are required. All 8 listed threads are addressed in the current HEAD (f71cd08):

Thread Disposition
r3652183109 IPv6/userinfo bypass OutdatedisHttpsAngleBracketAutolink uses strict [\w.-]+; rejection tests added
r3652200991 |-in-URL limitation Outdated — documented in the module-level comment block
r3652200993 Missing test for disallowed Slack mrkdwn ✅ Test should redact Slack mrkdwn links with disallowed domains (line 606) covers this
r3652201221 Label dropped on disallowed domain Outdated — callback returns `` `${filtered}
r3652201222 Missing label-preservation test ✅ Same test asserts expect(result).toContain("Build failure")
r3652207470 Regex allocated per call OutdatedangleBracketHttpsAutolinkRegex hoisted to module scope at line 40
r3652207471 No redaction test for blocked Slack link Outdated — same test as r3652200993 above
r3652207473 Duplicate "First pass" labels Outdated — renamed to "Pass 1" / "Pass 2" at lines 347/366

6 threads are is_outdated: true (GitHub auto-marks these when the diff lines change). The 2 non-outdated threads (r3652200993, r3652201222) are satisfied by existing tests; they remain unresolved only because the agent sandbox has no resolveReviewThread write access.

Remaining for maintainers:

  1. Dismiss the stale CHANGES_REQUESTED review (Matt Pocock Skills Reviewer) — all requests implemented
  2. Resolve the 2 non-outdated threads (r3652200993, r3652201222)
  3. Re-trigger CI (close/reopen or workflow_dispatch) for a green signal on HEAD f71cd08

@github-actions

Copy link
Copy Markdown
Contributor

🤖 PR Triage

Field Value
Category bug
Risk 🟢 low
Priority Score 45 / 100
Score Breakdown Impact: 20 · Urgency: 15 · Quality: 10
Recommended Action ⚡ fast_track

Summary: Safe-output sanitizer incorrectly rewrites Slack/CommonMark HTTPS autolinks <(redacted)|label> breaking downstream sinks. Low risk, isolated regex change with existing test coverage. CI passes (copilot ✅). Most recent review: CHANGES_REQUESTED from github-actions[bot] — verify those are addressed.

Next action: Resolve any outstanding review comments, then fast-track.

Generated by 🔧 PR Triage Agent · sonnet46 · 49 AIC · ⌖ 8.25 AIC · ⊞ 5.7K ·

@gh-aw-bot

Copy link
Copy Markdown
Collaborator

Copilot AI commented Jul 27, 2026

Copy link
Copy Markdown
Contributor Author

@copilot please run the pr-finisher skill, address the unresolved review feedback below, and update the PR with what still blocks merge readiness.

Unresolved feedback to close out (newest first):...

No further code changes are required. All 8 listed threads are fully addressed in the current HEAD (f71cd08).

Thread status:

Thread Status
r3652183109 IPv6/userinfo bypass Outdated — strict [\w.-]+ hostname rejects [ (IPv6) and @ (userinfo); tests added
r3652200991 |-in-URL path limitation Outdated — documented in module-level comment block
r3652200993 Missing test for disallowed Slack mrkdwn ✅ Covered by should redact Slack mrkdwn links with disallowed domains (line 606 of sanitize_content.test.cjs)
r3652201221 Label dropped on disallowed domain Outdated — callback returns `` `${filtered}
r3652201222 Missing label-preservation test ✅ Same test (expect(result).toContain("Build failure")) pins this behavior
r3652207470 Regex allocated per call OutdatedangleBracketHttpsAutolinkRegex hoisted to module scope at line 40
r3652207471 No redaction test for blocked Slack link Outdated — covered by same test as r3652200993
r3652207473 Duplicate "First pass" labels Outdated — renamed to "Pass 1" / "Pass 2" at lines 347/366

6 of 8 threads are is_outdated: true. The 2 non-outdated threads (r3652200993, r3652201222) are both satisfied by existing tests in HEAD; they remain unresolved only because this agent sandbox has no resolveReviewThread write access.

What blocks merge (maintainer action required):

  1. Dismiss the stale CHANGES_REQUESTED review (Matt Pocock Skills Reviewer bot) — all its requested changes are implemented in HEAD
  2. Resolve threads r3652200993 and r3652201222 (code satisfies both)
  3. Re-trigger CI (close/reopen or workflow_dispatch) for a green signal on HEAD f71cd08

@pelikhan

Copy link
Copy Markdown
Collaborator

@github-actions

Copy link
Copy Markdown
Contributor

🤖 PR Triage

Field Value
Category bug
Risk 🟢 low
Priority Score 65 / 100
Recommended Action ⚡ fast_track
CI Status 🟡 in-progress (19 pass, 1 fail of 30) — 1 failure: Windows Integration

Score breakdown: Impact 32/50 · Urgency 18/30 · Quality 15/20

Rationale: Slack/CommonMark autolinks (<(redacted)|label>) incorrectly sanitized as unknown HTML, breaking downstream rendering. Low-risk targeted fix (139+/4−, 4 files). AI reviewer approved (Test Quality 100/100). One CI failure on Windows Integration — needs investigation before merge.

Run §30246015734

Generated by 🔧 PR Triage Agent · sonnet46 · 36.5 AIC · ⌖ 5.31 AIC · ⊞ 5.7K ·

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>

Copilot AI commented Jul 27, 2026

Copy link
Copy Markdown
Contributor Author

@copilot fix https://github.com/github/gh-aw/actions/runs/30246080263/job/89913341584?pr=48169

Fixed in the latest commit. sanitize_output_fuzz_test.go:138 had (func(t *testing.T, ...) as a bare unused function literal instead of f.Fuzz(func(t *testing.T, ...). The integration-tagged build now passes locally (go test -run='^$' -exec=true -tags=integration ./pkg/workflow/... exits 0).

@pelikhan
pelikhan merged commit 16b3f49 into main Jul 27, 2026
35 checks passed
@pelikhan
pelikhan deleted the copilot/fix-slack-mrkdwn-links branch July 27, 2026 08:00
@github-actions

Copy link
Copy Markdown
Contributor

🎉 This pull request is included in a new release.

Release: v0.83.4

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Safe-outputs sanitizer breaks Slack mrkdwn links (<url|label>) in custom safe-job string inputs

4 participants